#include "StaticModel.hpp"

#include "gl.hpp"

#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

using namespace std;

#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include "Exception.hpp"

#include "MaterialLib.hpp"

using namespace boost;

//----------- helper structures for model loading ------------
struct float2
{
	float x, y;
};

struct float3
{
	float x, y, z;
};

struct Index
{
	int32_t Position, Texture, Normal;
};

struct Face//a triangle face
{
	Index I1, I2, I3;
};
//_________________________________________________________


string SubString(string String, int Pos1, int Pos2);
string GetPathFromFilename(string Filename);

template<typename t> void SaveVector(ofstream& File, std::vector<t>& vec);
template<typename t> void LoadVector(ifstream& File, std::vector<t>& vec);
void SaveString(ofstream& File, std::string String);
std::string LoadString(ifstream& File);


StaticModel::StaticModel():
	m_IndexCount(0)
{
}

StaticModel::~StaticModel()
{
}

//TODO: check if we have normals!
void StaticModel::Load(string Filename)
{
	try
	{
		m_Filename=Filename;

		//try to load the binary file:
		{
			ifstream  tmp((Filename+".bin").c_str());
			if(tmp.is_open())
			{
				tmp.close();
				LoadFromBinary(Filename+".bin");
				return;
			}
		}

		MaterialLib materials;

		ifstream File;

		string Path=GetPathFromFilename(Filename);

		cout << "Loading StaticModel " << Filename << endl;

		File.open(Filename.c_str());
		if(!File.is_open())
		{
			THROW(Exception() << ex_message("Model could not be loaded") << ex_file(Filename));
		}

		/*
		Da man nicht weiß wie viel wovon in der Datei vorhanden ist, werden die Daten jeweils
		in eine list gespeichert und hinterher entsprechend in den Speicher kopiert
		*/
		vector<float3> Positions;
		vector<float2> UVs;
		vector<float3> Normals;
		vector<Face> Faces;

		int SubmeshIndex=0;
		
		cout << "Lese Datei ein..." << endl;
		string Line;
		int ObjectCount=0;//wie viele Objekte in der Datei gespeichert wurden, damit man sichergehen kann, das nur eins geöffnet wurde
		while(!File.eof())
		{
			getline(File, Line);
			boost::trim_if(Line, is_any_of(" \n\r"));//remove whitespaces
			if(Line.length()==0)//skip emtpy lines
				continue;

			vector<string> SplitedLine;
			boost::split(SplitedLine, Line, is_any_of(" "));

			if("#"==SplitedLine[0])//zeilen die Kommentare sind werden übersprungen
				continue;

			else if("mtllib"==SplitedLine[0])//load the material lib:
			{
				materials.Load(SplitedLine[1], GetPathFromFilename(Filename));
			}

			else if("o"==SplitedLine[0])//zeichen dass ein neues objekt beginnt
			{
				ObjectCount++;
				if(ObjectCount>1)
					cerr << "WARNUNG: Datei enthält mehr als ein Objekt. Dies wird NICHT unterstützt!!" << endl;
				else
					cout << "Lade Objekt " << Line.substr(2, string::npos) << endl;
			}

			else if("v"==SplitedLine[0])//eine Position
			{
				float3 Position; //eine neue Position
				Position.x=lexical_cast<float>(SplitedLine[1]);
				Position.y=lexical_cast<float>(SplitedLine[2]);
				Position.z=lexical_cast<float>(SplitedLine[3]);
				Positions.push_back(Position);
			}

			else if("vt"==SplitedLine[0])//eine Texturkoordinate
			{
				float2 UV;
				UV.x=lexical_cast<float>(SplitedLine[1]);
				UV.y=lexical_cast<float>(SplitedLine[2]);
				UVs.push_back(UV);
			}

			else if("vn"==SplitedLine[0])//eine Normale
			{
				float3 Normal; //eine neue Position
				Normal.x=lexical_cast<float>(SplitedLine[1]);
				Normal.y=lexical_cast<float>(SplitedLine[2]);
				Normal.z=lexical_cast<float>(SplitedLine[3]);
				Normals.push_back(Normal);
			}

			else if("f"==SplitedLine[0])//ein Face
			{
				/*Ein Face besteht aus mehreren Punkten zu denen jeweils die Indexe zu Position/Textur/Normale angegeben sind*/
				Index I[3];
				Face NewFace;
				for(unsigned int i=0; i<3; ++i)
				{
					vector<string> indicies;
					split(indicies, SplitedLine[1+i], is_any_of("/"));
					if(indicies.size()!=3)
					{
						THROW(Exception() << ex_message("face values are not corrects!") << ex_detail(Line));
					}

					try
					{
						I[i].Position=	lexical_cast<unsigned int>(indicies[0]);
						I[i].Texture =	lexical_cast<unsigned int>(indicies[1]);
						I[i].Normal  =	lexical_cast<unsigned int>(indicies[2]);
					}
					catch(...)
					{
						THROW(Exception() << ex_message("line could not be parsed") << ex_detail(Line));
					}
				}
				NewFace.I1=I[0];
				NewFace.I2=I[1];
				NewFace.I3=I[2];
				Faces.push_back(NewFace);
				SubmeshIndex++;

				//check if it was a quad
				if(SplitedLine.size()==5)
				{
					//do the same as above:
					//CAREFULL: CODE DUPLICATION, REMEMBER THIS WHEN CHANGING ANYTHING!!!!
					Index I[3];
					Face NewFace;
					for(unsigned int i=0; i<3; ++i)
					{
						vector<string> indicies;
						split(indicies, SplitedLine[1+(i+2)%4], is_any_of("/"));//(i+2)%4 to get 2,3,0 from 0,1,2

						I[i].Position=	lexical_cast<unsigned int>(indicies[0]);
						I[i].Texture =	lexical_cast<unsigned int>(indicies[1]);
						I[i].Normal  =	lexical_cast<unsigned int>(indicies[2]);
					}
					NewFace.I1=I[0];
					NewFace.I2=I[1];
					NewFace.I3=I[2];
					Faces.push_back(NewFace);
					SubmeshIndex++;
				}

			}

			else if("usemtl"==SplitedLine[0])
			{
				//create the new submesh
				Submesh NewSubmesh;
				NewSubmesh.Index=SubmeshIndex;

				NewSubmesh.Mat=materials.GetMaterial(SplitedLine[1]);

				m_Submeshs.push_back(NewSubmesh);
			}

			else if("s"==SplitedLine[0]) //well, we just ignore smooth groups :)
			{
			}

			else
			{
				THROW(Exception() << ex_message("line could not be parsed") << ex_detail(Line));
			}

		}

		//append the last index
		Submesh NewSubmesh;
		NewSubmesh.Index=SubmeshIndex;

		m_Submeshs.push_back(NewSubmesh);




		cout << "Datei gelesen" << endl;
		cout << Positions.size() << " Vertexpositionen geladen!" << endl;
		cout << Normals.size() << " Vertexnormalen geladen!" << endl;
		cout << UVs.size() << " Texturkoordinaten geladen!" << endl;
		cout << Faces.size() << " Faces geladen! " << endl;


		//da wir jetzt die datei komplett geladen und geparst haben, müssen wir jetzt die Daten in die Speicherbereiche schreiben
		//aus der Liste renderts sich so unperformant
		m_IndexCount=Faces.size()*3;

		m_Positions.resize(3*m_IndexCount);
		m_Normals.resize(3*m_IndexCount);
		m_UVs.resize(2*m_IndexCount);
		m_Indices.resize(m_IndexCount);


		cout << "Verarbeite Daten..." << endl;

		int Pos;
		int i=0;
		//-------------------------den Positionsbuffer füllen:-----------------------
		for(vector<Face>::iterator ItFaces=Faces.begin(); ItFaces!=Faces.end(); ++ItFaces)
		{
			Pos=ItFaces->I1.Position-1;//they count from 1...
			m_Positions[i+0]=Positions[Pos].x;
			m_Positions[i+1]=Positions[Pos].y;
			m_Positions[i+2]=Positions[Pos].z;
			i+=3;
			
			Pos=ItFaces->I2.Position-1;//they count from 1...
			m_Positions[i+0]=Positions[Pos].x;
			m_Positions[i+1]=Positions[Pos].y;
			m_Positions[i+2]=Positions[Pos].z;
			i+=3;
			
			Pos=ItFaces->I3.Position-1;//they count from 1...
			m_Positions[i+0]=Positions[Pos].x;
			m_Positions[i+1]=Positions[Pos].y;
			m_Positions[i+2]=Positions[Pos].z;
			i+=3;
		}


		//-----------------------die Indices schreiben:-------------------------
		for(int i=0; i<m_IndexCount; ++i)
		{
			m_Indices[i]=i;
		}



		

		//----------------einen texturbuffer erstellen:---------------------
		i=0;
		for(vector<Face>::iterator ItFaces=Faces.begin(); ItFaces!=Faces.end(); ++ItFaces)
		{
			Pos=ItFaces->I1.Texture-1;//they count from 1...
			m_UVs[i+0]=UVs[Pos].x;
			m_UVs[i+1]=UVs[Pos].y;
			i+=2;
			
			Pos=ItFaces->I2.Texture-1;//they count from 1...
			m_UVs[i+0]=UVs[Pos].x;
			m_UVs[i+1]=UVs[Pos].y;
			i+=2;
			
			Pos=ItFaces->I3.Texture-1;//they count from 1...
			m_UVs[i+0]=UVs[Pos].x;
			m_UVs[i+1]=UVs[Pos].y;
			i+=2;
		}



		//-------------------------einen Normalbuffer erstellen:--------------------------
		i=0;for(vector<Face>::iterator ItFaces=Faces.begin(); ItFaces!=Faces.end(); ++ItFaces)
		{
			Pos=ItFaces->I1.Normal-1;//they count from 1...
			m_Normals[i+0]=Normals[Pos].x;
			m_Normals[i+1]=Normals[Pos].y;
			m_Normals[i+2]=Normals[Pos].z;
			i+=3;
			
			Pos=ItFaces->I2.Normal-1;//they count from 1...
			m_Normals[i+0]=Normals[Pos].x;
			m_Normals[i+1]=Normals[Pos].y;
			m_Normals[i+2]=Normals[Pos].z;
			i+=3;
			
			Pos=ItFaces->I3.Normal-1;//they count from 1...
			m_Normals[i+0]=Normals[Pos].x;
			m_Normals[i+1]=Normals[Pos].y;
			m_Normals[i+2]=Normals[Pos].z;
			i+=3;
		}


		//Debugausgabe für die submeshs:
		cout << "Submeshs:" << endl;
		for(unsigned int i=0; i<m_Submeshs.size(); ++i)
		{
			cout << "Index: " << m_Submeshs[i].Index << endl;
		}

		cout << "Model " << Filename << " successfully loaded!" << endl;

		//and write the binary file:
		SaveToBinary(Filename+".bin");
	}
	catch(boost::exception &ex)
	{
		ex << ex_model(Filename);
		throw;
	}
}

void StaticModel::RenderModel()
{
	if(m_IndexCount==0)
		return;

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	glVertexPointer(3, GL_FLOAT, 0, &m_Positions[0]);
	glTexCoordPointer(2, GL_FLOAT, 0,&m_UVs[0]);
	glNormalPointer(GL_FLOAT, 0, &m_Normals[0]);

	for(vector<Submesh>::iterator It=m_Submeshs.begin(); It!=(--m_Submeshs.end()); It++)//we need upper bound and lower bound, so we have only n-1 submeshs
	{
		int LastIndex=It->Index;
		It->Mat.Activate();

		//this is important for rendering, because we need two indexes (from, to)
		vector<Submesh>::iterator NextIt=It;
		NextIt++;

		glDrawElements(GL_TRIANGLES, (NextIt->Index-LastIndex)*3, GL_UNSIGNED_INT, &m_Indices[0]+(LastIndex*3));
		It->Mat.Deactivate();
	}


	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
}

CollisionModel StaticModel::CalculateCollisionBox()
{
	//initialized min/max values
	Vector3f Min(m_Positions[0], m_Positions[1], m_Positions[2]), Max(Min);

	for(unsigned int i=0; i<m_Positions.size(); i+=3)
	{
		if(m_Positions[i+0]<Min.x)
			Min.x=m_Positions[i+0];
		if(m_Positions[i+1]<Min.y)
			Min.y=m_Positions[i+1];
		if(m_Positions[i+2]<Min.z)
			Min.z=m_Positions[i+2];

		if(m_Positions[i+0]>Max.x)
			Max.x=m_Positions[i+0];
		if(m_Positions[i+1]>Max.y)
			Max.y=m_Positions[i+1];
		if(m_Positions[i+2]>Max.z)
			Max.z=m_Positions[i+2];
	}

	return CollisionModel(Min, Max);
}

void StaticModel::SaveToBinary(string Filename)
{
	cout << "Saving " << Filename << endl;
	ofstream File;

	File.open(Filename.c_str(), ios_base::trunc | ios_base::binary);

	SaveVector(File, m_Positions);
	SaveVector(File, m_Normals);
	SaveVector(File, m_UVs);
	SaveVector(File, m_Indices);

	//save submeshes:
	size_t Size=  m_Submeshs.size();
	File.write((char*)&Size, sizeof(size_t));
	for(Submesh& Mesh : m_Submeshs)
	{
		File.write((char*)&Mesh.Index, sizeof(Mesh.Index));
		SaveString(File, Mesh.Mat.GetTextureFilename());
	}
}

void StaticModel::LoadFromBinary(string Filename)
{
	cout << "Loading " << Filename << endl;

	ifstream File;

	File.open(Filename.c_str(), ios_base::binary);

	LoadVector(File, m_Positions);
	LoadVector(File, m_Normals);
	LoadVector(File, m_UVs);
	LoadVector(File, m_Indices);

	//load submeshes:
	int32_t Size;
	File.read((char*)&Size, sizeof(int32_t));
	m_Submeshs.resize(Size);
	for(Submesh& Mesh : m_Submeshs)
	{
		File.read((char*)&Mesh.Index, sizeof(Mesh.Index));
		string Tex = LoadString(File);
		if(Tex.length() > 0)
			Mesh.Mat.LoadTexture(Tex);
	}

	//NOTE: Very ugly hack, but pfff
	m_IndexCount = 1;
}


///--- helper functions ---

string SubString(string String, int Pos1, int Pos2)
{
	return String.substr(Pos1, Pos2-Pos1);
}


string GetPathFromFilename(std::string Filename)
{
	if(string::npos != Filename.find_last_of('/'))
		return Filename.substr(0, Filename.find_last_of('/'))+"/";
	else if(string::npos != Filename.find_last_of('\\'))
		return Filename.substr(0, Filename.find_last_of('\\'))+"\\";
	else
		return string();
}

template<typename t> void SaveVector(ofstream& File, std::vector<t>& vec)
{
	int32_t Size = vec.size();
	File.write((char*)&Size, sizeof(int32_t));
	File.write((char*)&vec[0], sizeof(t)*Size);
}

template<typename t> void LoadVector(ifstream& File, std::vector<t>& vec)
{
	int32_t Size;
	File.read((char*)&Size, sizeof(int32_t));
	vec.resize(Size);
	File.read((char*)&vec[0], sizeof(t)*Size);
}

void SaveString(ofstream& File, std::string String)
{
	int32_t Size = String.length();
	File.write((char*)&Size, sizeof(int32_t));
	File.write(String.c_str(), sizeof(char)*Size);
}

std::string LoadString(ifstream& File)
{
	string Res;

	int32_t Size;
	File.read((char*)&Size, sizeof(int32_t));
	char* buf=new char[Size+1];

	File.read(buf, sizeof(char)*Size);
	buf[Size]='\0';
	Res=buf;

	return Res;
}